/*  webdav.js, version 0.1 
 *  (c) 2006 Shinichi Tomita <shinichi.tomita@hotmail.com>
 *
 *  webdav.js is freely distributable under the terms of an MIT-style license.
/*--------------------------------------------------------------------------*/

var WebDAV = {

  __createXMLHttpRequest : function() {
    return window.XMLHttpRequest ? 
      new XMLHttpRequest() : new ActiveXObject("Microsoft.XMLHTTP");
  }
  ,

  __xml2obj : function(doc) {

    var o = dom2obj(doc.documentElement);
    var obj = {};
    obj[o.tag] = o.value;
    return obj;

    function dom2obj(elem) {
      if (elem.nodeType==3) return elem.data;
      else if (elem.nodeType==1) {
        var obj = {};
        var hasprops = false;
        for (var i=0; i<elem.attributes.length; i++) {
          hasprops = true;
          var attr = elem.attributes[i];
          if (attr.nodeName.match(/^xmlns/)) continue;
          obj[attr.nodeName] = attr.nodeValue;
        }
        for (var i=0; i<elem.childNodes.length; i++) {
          var childObj = dom2obj(elem.childNodes[i]);
          if (childObj.tag) {
            hasprops = true;
            if (obj[childObj.tag]) {
              if (obj[childObj.tag].constructor != Array) {
                obj[childObj.tag] = [ obj[childObj.tag] ];
              }
              obj[childObj.tag].push(childObj.value);
            } else {
              obj[childObj.tag] = childObj.value;
            }
          } else { // text content
            obj['#'] = childObj;
          }
        }
        if (!hasprops) obj = obj['#'] ? obj['#'] : null;
        return { tag : elem.tagName.replace(/^\w+:/, ''), value : obj };
      }
    }
  }
  ,

  doHead : function(path, headers, callback, errback) {
    var me = this;
    var req = this.__createXMLHttpRequest();
    req.onreadystatechange = function() {
      if (req.readyState == 4) {
        if (req.status==200) {
          if (callback) callback(req);
        } else {
          if (errback) errback(req);
        }
      }
    }
    req.open('HEAD', path, true);
    headers = headers || {};
    for (h in headers) {
      req.setRequestHeader(h, headers[h]);
    }
    req.send(null);
  }
  ,

  doGet : function(path, headers, callback, errback) {
    var me = this;
    var req = this.__createXMLHttpRequest();
    req.onreadystatechange = function() {
      if (req.readyState == 4) {
        if (req.status==200) {
          if (callback) callback(req, req.responseText);
        } else {
          if (errback) errback(req);
        }
      }
    }
    req.open('GET', path, true);
    headers = headers || {};
    for (h in headers) {
      req.setRequestHeader(h, headers[h]);
    }
    req.send(null);
  }
  ,

  doPost : function(path, headers, data, callback, errback) {
    var me = this;
    var req = this.__createXMLHttpRequest();
    req.onreadystatechange = function() {
      if (req.readyState == 4) {
        if (req.status==200 || req.status==201 || req.status==204) {
          if (callback) callback(req, req.responseText);
        } else {
          if (errback) errback(req);
        }
      }
    }
    req.open('POST', path, true);
    headers = headers || {};
    headers['Depth'] = headers['Depth']>1 ? 1 : headers['Depth'];
    headers['Content-Type'] = headers['Content-Type'] || 'text/xml';
    for (h in headers) {
      req.setRequestHeader(h, headers[h]);
    }
    req.send(data);
  }
  ,

  doPut : function(path, headers, data, callback, errback) {
    var me = this;
    var req = this.__createXMLHttpRequest();
    req.onreadystatechange = function() {
      if (req.readyState == 4) {
        if (req.status==200 || req.status==201 || req.status==204) {
          if (callback) callback(req);
        } else {
          if (errback) errback(req);
        }
      }
    }
    req.open('PUT', path, true);
    headers = headers || {};
    for (var h in headers) {
      req.setRequestHeader(h, headers[h]);
    }
    req.send(data);
  }
  ,

  doDelete : function(path, headers, callback, errback) {
    var me = this;
    var req = this.__createXMLHttpRequest();
    req.onreadystatechange = function() {
      if (req.readyState == 4) {
        if (req.status==200 || req.status==207) {
          if (callback) callback(req);
        } else {
          if (errback) errback(req);
        }
      }
    }
    req.open('DELETE', path, true);
    headers = headers || {};
    for (h in headers) {
      req.setRequestHeader(h, headers[h]);
    }
    req.send(null);
  }
  ,

  doLock : function(path, headers, callback, errback) {
    var me = this;
    var req = this.__createXMLHttpRequest();
    req.onreadystatechange = function() {
      if (req.readyState == 4) {
        if (req.status==200) {
          var res = me.__xml2obj(req.responseXML);
          if (callback) callback(req, res.prop.lockdiscovery.activelock.locktoken.href);
        } else {
          if (errback) errback(req);
        }
      }
    }
    req.open('LOCK', path, true);
    headers = headers || {};
    headers['Depth'] = headers['Depth']>1 ? 1 : headers['Depth'];
    headers['Content-Type'] = headers['Content-Type'] || 'text/xml';
    for (h in headers) {
      req.setRequestHeader(h, headers[h]);
    }
    req.send('<?xml version="1.0" ?><lockinfo xmlns="DAV:"><lockscope><exclusive/></lockscope><locktype><write/></locktype><owner></owner></lockinfo>');
  }
  ,

  doUnlock : function(path, headers, callback, errback) {
    var me = this;
    var req = this.__createXMLHttpRequest();
    req.onreadystatechange = function() {
      if (req.readyState == 4) {
        if (req.status==204) {
          if (callback) callback(req);
        } else {
          if (errback) errback(req);
        }
      }
    }
    req.open('UNLOCK', path, true);
    headers = headers || {};
    for (h in headers) {
      req.setRequestHeader(h, headers[h]);
    }
    req.send(null);
  }
  ,

  doOptions : function(path, headers, callback, errback) {
    var me = this;
    var req = this.__createXMLHttpRequest();
    req.onreadystatechange = function() {
      if (req.readyState == 4) {
        if (req.status==200) {
          var allowedMethods = req.getResponseHeader("Allow");
          if (callbck) callback(req, allowedMethods);
        } else {
          if (errback) errback(req);
        }
      }
    }
    req.open('OPTIONS', path, true);
    headers = headers || {};
    for (h in headers) {
      req.setRequestHeader(h, headers[h]);
    }
    req.send(null);
  }
  ,

  doPropfind : function(path, headers, callback, errback) {
    var me = this;
    var req = this.__createXMLHttpRequest();
    req.onreadystatechange = function() {
      if (req.readyState == 4) {
        if (req.status==207) {
          var res = me.__xml2obj(req.responseXML);
          if (callback) callback(req, res);
        } else {
          if (errback) errback(req);
        }
      }
    }
    req.open('PROPFIND', path, true);
    headers = headers || {};
    headers['Depth'] = headers['Depth']>1 ? 1 : headers['Depth'];
    headers['Content-Type'] = headers['Content-Type'] || 'text/xml';
    for (h in headers) {
      req.setRequestHeader(h, headers[h]);
    }
    req.send('<?xml version="1.0" ?><propfind xmlns="DAV:"><allprop/></propfind>');
  }

}


/**
 *
 */
var File = function(path) {
  this.href = path;
  this._isLoaded = false;
  this._isPropLoaded = false;
}

File.prototype = {

  isFile : function() {
    if (this._isPropLoaded) {
      return this.props['resourcetype']==null;
    }
  }
  ,

  isDirectory : function() {
    if (this._isPropLoaded) {
      return this.props['resourcetype']!=null 
          && typeof this.props['resourcetype'].collection != 'undefined';
    }
  }
  ,

  isLocked : function() {
    if (this._isPropLoaded) {
      return this.props['lockdiscovery']!=null
    }
  }
  ,

  propNames : function() {
    if (this._isPropLoaded) {
      var propnames = [];
      for (var prop in this.props) {
        propnames.push(prop);
      }
      return propnames;
    }
  }
  ,

  list : function(callback) {
    var d = this;
    WebDAV.doPropfind(
      d.href,              /* path */
      { 'Depth' : 1 },     /* headers */
      function(req, res) { /* callback */
        var files = [];
        for (var i=0; i<res.multistatus.response.length; i++) {
          var response = res.multistatus.response[i];
          var file = response.href == d.href ? d : new File(response.href);
          file.props = {};
          for (var propname in response.propstat.prop) {
            file.props[propname] = response.propstat.prop[propname];
          }
          file._isPropLoaded = true;
          if (file!=d) files.push(file);
        }
        callback(files);
      },
      function(req) {      /* errback */
        callback(null);
      }
    );
  }
  ,

  check : function(callback) {
    var f = this;
    WebDAV.doHead(
      f.href,
      { 'Cache-Control'     : 'max-age=0' ,
        'If-Modified-Since' : 'Thu, 01 Jan 1970 00:00:00 GMT' },
      function(req) { callback(true) },
      function(req) { callback(false) }
    );
  }
  ,

  remove : function(callback) {
    var f = this;
    WebDAV.doDelete(
      f.href,              /* path */
      null,                /* headers */
      function(req, res) { /* callback */
        callback(true);
      },
      function(req, res) { /* errback */
        callback(false);
      }
    );
  }
  ,

  load : function(callback) {
    var f = this;

    var procs = 2;
    WebDAV.doGet(
      f.href,
      { 'Cache-Control'     : 'max-age=0' ,
        'If-Modified-Since' : 'Thu, 01 Jan 1970 00:00:00 GMT' },
      function(req, content){ 
        f._isLoaded = true;
        f.content = content;
        procs--;
        if (procs==0) callback(f) 
      },
      function() { callback(false) }
    );

    if (f._isPropLoaded) {
      procs--;
    } else {
      WebDAV.doPropfind(
        f.href,
        { 'Depth' : 0 },
        function(req, res){ 
          f.props = {};
          var response = res.multistatus.response;
          for (var propname in response.propstat.prop) {
            f.props[propname] = response.propstat.prop[propname];
          }
          f._isPropLoaded = true;
          procs--;
          if (procs==0) callback(f) 
        },
        function() { callback(false) }
      );
    }

  }
  ,

  
  save : function(callback) {
    var f = this;
    
    function check(next) {
      f.check(next);
    }

    function lock(next) {
      WebDAV.doLock(
        f.href, 
        { 'Timeout' : 'Second-60' }, 
        function(req, token) {
          next(token);
        },
        abort
      );
    }
 
    function put(next, locktoken) {
      var headers = { 
        'Cache-Control'     : 'max-age=0' ,
        'If-Modified-Since' : 'Thu, 01 Jan 1970 00:00:00 GMT' 
      };
      headers['Content-Type'] = 
        f.contentType || f.props && f.props.getcontenttype || 'text/plain';
      if (locktoken) {
        headers['If'] = '(<'+locktoken+'>)';
      }
      WebDAV.doPut(
        f.href,
        headers,
        f.content,
        function() { next(locktoken) },
        abort
      );
    }

    function unlock(next, locktoken) {
      WebDAV.doUnlock(
        f.href, 
        { 'Lock-Token' : '<'+locktoken+'>' },
        next,
        abort
      );
    }

    function abort(req) {
      callback(false);
    }

    // first check file, if exists, lock->put->unlock, otherwise, just put.
    check(function(exists) {
      if (exists) {
        lock(function(locktoken) {
          put(function(locktoken) {
            unlock(function() {
              callback(f);
            }, locktoken);
          }, locktoken);
        })
      } else {
        put(function() {
          callback(f);
        });
      }
    });

  }

}

